package com.example.haxballandroid;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.concurrent.Semaphore;

import org.andengine.util.debug.Debug.DebugLevel;

import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

import phone.beans.*;
import server.constants.OutgoingConstants;

import com.google.gson.Gson;
import com.google.gson.JsonObject;

/**
 * Acts as middle man between GUI/controllers and server to allow communication. Calling startService() from activities
 * multiple times will not generate multiple services. Only one service is active during the application lifetime.
 * The service is created in the initial activity: UsernameActivity, and subsequently called within each activity
 * that requires communication with the server. Activites can simply bind to the service to communicate with it, 
 * however the startService method is used for simplicity.
 *
 * To implement at the end: proper PrintStream, socket and Service termination.  
 */


public class ActionUpdate extends Service {
	
	public static final  String MESSAGE_TAG = "com.example.haxballandroid.MESSAGE"; //MESSAGE_TAG is universal across all activities for sending a username, join or create message
	public static final String BROADCAST_ACTION = "com.example.haxballandroid.actionupdate";
	public static final int SERVERSOCKET_PORT = 7999; // Default port in which this LobbyServer resides on
	// Default server IP address - if running AVD and server on same machine, use the IP below
	
	public static final String SERVER_IP = "10.0.2.2"; //SET THIS TO THE LOBBY SERVER IP
	
	//Default messages for receiving and placing messages inside intents
	public static GameServerBean updateBean; //used to pass updates to GameField
	public static GameServerBean[] gBeans;
	public static boolean joinStatus;
	public static boolean createStatus = false;
	public static boolean receiveGameUpdate = false;
	public static String username; //current player's username
	private Intent intentOutgoing;
	private BufferedReader in;
	private PrintStream out;
	private Socket socket;
	private Semaphore sem ; 
	/**
	* This method is run once the app is launched and will attempt to establish a connection to the 
	* server via a helper thread below.
	*/
	@Override
	public void onCreate(){
		super.onCreate();
		sem = new Semaphore(0, true);
		intentOutgoing = new Intent(BROADCAST_ACTION);
		Thread serverConnect = new Thread(new establishConnection());
		serverConnect.start();
		Thread receiveUpdates = new Thread(new broadcastUpdates());
		receiveUpdates.start();
	
	}
	/**
	* Called every time an activity requires communication with the server via startService(). 
	* 
	*/
	@Override
	public void onStart(Intent intent, int startID){
		try{ //TODO cleanup (check if bundle is null)
			Bundle bundle = intent.getExtras();
			broadcastServer(bundle.getString(MESSAGE_TAG), intent);
		
		}
		catch(NullPointerException e){
		}
	}
	/**
	* Method used to pass update intent to UI (to JoinActivity, CreateActivity, GameFieldActivity)
	*/
	private void BroadcastInfo(){
		intentOutgoing.putExtra(BROADCAST_ACTION, "update");
		sendBroadcast(intentOutgoing); //broadcast message to any listening activity (menu or game field)
	
	}
	
	private void BroadcastInfo(String message){
		intentOutgoing.putExtra(BROADCAST_ACTION, message);
		sendBroadcast(intentOutgoing);
	}
	/**
	* Ignore for now - not being used
	*/
	public IBinder onBind(Intent intent){
		return null;
	}
	@Override 
	public void onDestroy() {
		super.onDestroy();
	}
	/**
	* HELPER THREADS
	* These threads will run in the background and help establish a connection and send/receive
	* messages to and from the server. These actions must be run in their own threads and cannot 
	* be run in the main UI thread due to timeout issues.
	*
	*/
	private class establishConnection implements Runnable{
		public void run(){
			try {
				socket = new Socket(SERVER_IP,SERVERSOCKET_PORT); //TODO change tempIP back to SERVER_IP 
				out = new PrintStream(socket.getOutputStream());
				InputStreamReader is = new InputStreamReader(socket.getInputStream());
				if (is == null){
					org.andengine.util.debug.Debug.log(DebugLevel.ERROR, "ISReader is null before instantiating input stream");
			}
			
			in = new BufferedReader(is);
			sem.release(2);
			if (in == null){
				//System.out.println("input reader failed to initialize") ;
				org.andengine.util.debug.Debug.log(DebugLevel.ERROR, "input stream was not initailized first");
			} else {
				org.andengine.util.debug.Debug.log(DebugLevel.ERROR, "input stream success first");
			
			}
			if (out == null){
				System.out.println("output reader failed to initialize");
			}
			} catch (IOException e) {
				System.err.println("Could not connect to: " + SERVER_IP + " on port " + SERVERSOCKET_PORT);
			}
		}
	}
	/**
	* Creates a JSON join object with the server name and the appropriate password
	* @param game
	* @param password
	* @return
	*/
	public String getJSONJoin(String game, String password){
		JsonObject json = new JsonObject();
		JsonObject jsonInner = new JsonObject();
		jsonInner.addProperty("name", game);
		jsonInner.addProperty("password", password);
		json.add("gameDetails", jsonInner);
		json.addProperty("type", 13);
		return json.toString();
	}
	/**
	* Creates a JSON Object for creating a game with game name and a password
	* @param game
	* @param password
	* @return
	*/
	public String getJSONCreate(String game, String password, int score, int time){
		JsonObject json = new JsonObject();
		JsonObject jsonInner = new JsonObject();
		jsonInner.addProperty("name", game);
		jsonInner.addProperty("password", password);
		jsonInner.addProperty("timeLimit", time);
		jsonInner.addProperty("scoreLimit", score);
		json.addProperty("type", 12);
		json.add("gameDetails", jsonInner);
		return json.toString();
	}
	/**
	* Creates a JSON object for sending the username 
	*/
	public String getJSONUsername(String name){
		JsonObject json = new JsonObject();
		JsonObject jsonInner = new JsonObject();
		jsonInner.addProperty("name", name);
		json.addProperty("type", 11);
		json.add("gameDetails", jsonInner);
		return json.toString();
	}
	/**
	* Creates a JSON object for sending the refresh message to the lobby
	*/
	public String getJSONLobbyRefresh(){
		JsonObject json = new JsonObject();
		json.addProperty("type", 14);
		return json.toString();
	}
	/**
	* Creates a JSON object for sending kick action to the game server
	*/
	public String getJSONKick(){
		JsonObject json = new JsonObject();
		json.addProperty("type", 15);
		return json.toString();
	}
	/**
	* Creates a JSON object for changing teams for a player
	*/
	public String getJSONTeamChange(String player, int team){
		JsonObject json = new JsonObject();
		JsonObject jsonInner = new JsonObject();
		jsonInner.addProperty("name", player);
		jsonInner.addProperty("team", team);
		json.addProperty("type", 18);
		json.add("gameDetails", jsonInner);
		return json.toString();
	}
	/**
	* Creates a JSON object for leaving the game
	*/
	public String getJSONLeave(){
		JsonObject json = new JsonObject();
		json.addProperty("type", 17);
		return json.toString();
	}
	/**
	* Creates a JSON object for moving the player 
	*/
	public String getJSONMove(float x, float y){
		JsonObject json = new JsonObject();
		JsonObject jsonInner = new JsonObject();
		jsonInner.addProperty("x", x);
		jsonInner.addProperty("y", y);
		json.addProperty("type", 16);
		json.add("gameDetails", jsonInner);
		return json.toString();
	}
	/**
	* Creates a JSON object for changing the time limit
	*/
	public String getJSONTimeLimit(int time){
		JsonObject json = new JsonObject();
		JsonObject jsonInner = new JsonObject();
		jsonInner.addProperty("timeLimit", time);
		json.addProperty("type", 19);
		json.add("gameDetails", jsonInner);
		return json.toString();
	}
	/**
	* Creates a JSON object for changing the score limit
	*/
	public String getJSONScoreLimit(int score){
		JsonObject json = new JsonObject();
		JsonObject jsonInner = new JsonObject();
		jsonInner.addProperty("scoreLimit", score);
		json.addProperty("type", 20);
		json.add("gameDetails", jsonInner);
		return json.toString();
	}
	/**
	* 
	* Used to send messages to server. No sleep delay - sends updates
	* immediately. 
	*
	*/
	public void broadcastServer(String msg, Intent intent){
		String message = msg;
		Bundle bundle = intent.getExtras();
		try{
			if(message.equalsIgnoreCase("create")){
				out.println(this.getJSONCreate(bundle.getString("GameName"), bundle.getString("Password"), bundle.getInt("ScoreLimit"), bundle.getInt("TimeLimit")));
			}
			if(message.equalsIgnoreCase("join")){
				out.println(this.getJSONJoin(bundle.getString("GameName"), bundle.getString("Password")));
			}
			if(message.equalsIgnoreCase("username")){
				out.println(this.getJSONUsername(bundle.getString("UserName")));
				username = bundle.getString("UserName");
			}
			if(message.equalsIgnoreCase("kick")){
				out.println(this.getJSONKick());
			}
			if(message.equalsIgnoreCase("refresh")){
				out.println(this.getJSONLobbyRefresh());
			}
			if(message.equalsIgnoreCase("teamChange")){
				out.println(this.getJSONTeamChange(bundle.getString("Player"), bundle.getInt("TeamChange")));
			}
			if(message.equalsIgnoreCase("move")){
				out.println(this.getJSONMove(bundle.getFloat("X"), bundle.getFloat("Y")));
			}
			if(message.equalsIgnoreCase("leave")){
				out.println(this.getJSONLeave());
			}
			if(message.equalsIgnoreCase("timeLimit")){
				out.println(this.getJSONTimeLimit(bundle.getInt("TimeLimit")));
			}
			if(message.equalsIgnoreCase("scoreLimit")){
				out.println(this.getJSONScoreLimit(bundle.getInt("ScoreLimit")));
			}
			// out.println(msg);
			out.flush();
			
		}
		catch(NullPointerException e){
		}
		
	}
	/**
	* This is used to receive server updates and send them to the UI (JoinActivity, CreateActivity, GameFieldActivity).
	* It runs continuously at the interval provided - to see the other end of how this works, see GameFieldActivity,
	* specifically broadcastReceiver and updateUI. Will be destroyed by the service upon game termination. 
	*/
	//Receives updates at a given interval and sends to UI via DisplayInfo. Change update interval to desired value.
	private class broadcastUpdates implements Runnable{
		String incoming = "";
		public void run() {
			try{
			Gson gson = new Gson();
			GameServerBean gsBean = null;
			GameServerBean[] beans = null;
			try {
				// TODO parse  incoming JSON
				if (in == null){
				//in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				org.andengine.util.debug.Debug.log(DebugLevel.ERROR, "input stream was not initailized second");
				}
				try {
					sem.acquire();
					} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					}
					while((incoming = in.readLine()) != null){
//						Log.d("Message", incoming);
						MessageBean bean = gson.fromJson(incoming, MessageBean.class);
						int type = bean.getType();
						switch(type) {
						case OutgoingConstants.GAMESERVER_UPDATE:
							updateBean = bean.getGameServer();
							receiveGameUpdate = true;
							BroadcastInfo();
							break;
						case OutgoingConstants.USERNAME_SUCCESSFUL:
							System.out.println("Username Successful");
							break;
						case OutgoingConstants.CREATE_SUCCESSFUL:
							Log.d("Update", incoming);
//							updateBean = bean.getGameServer();	
							createStatus = true;
							BroadcastInfo();
							System.out.println("Game Created");
							break;
						case OutgoingConstants.CREATE_UNSUCCESFUL:
							System.out.println("Game cannot be created");
							
						case OutgoingConstants.NO_GAME_EXISTS:
							BroadcastInfo("join");
							System.out.println("No game exists");
							break;
						case OutgoingConstants.GAME_FULL:
							BroadcastInfo("join");
							System.out.println("Game Full");
							break;
						case OutgoingConstants.WRONG_PASSWORD:
							joinStatus = false;
							BroadcastInfo("join");
							System.out.println("Wrong Password, Try Again");
							break;
						case OutgoingConstants.JOIN_SUCCESSFUL:
							gsBean = bean.getGameServer();
							joinStatus = true;
							BroadcastInfo("join");
							System.out.println("Game Joined Successfully");
							break;
							
						case OutgoingConstants.REFRESH_GAMESERVERS:
							gBeans = bean.getGameServers();
							BroadcastInfo("refresh");
							System.out.println("List of Game Servers");
							break;
						case OutgoingConstants.INVALID_MESSAGE:
							System.out.println("Invalid Message");
							break;
					}
				}
				} catch (IOException e) {
					e.printStackTrace();
				}
				org.andengine.util.debug.Debug.log(DebugLevel.ERROR, "incomoing message thread ended");
			}
			catch(NullPointerException e){
				e.printStackTrace();
			}
		}
	
	}
}//end class